﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Numerics;
using SpiceNet.Circuits;
using MathNet.Numerics.LinearAlgebra;
using SpiceNet.Diagnostics;
using SpiceNet.Simulations;
using SpiceNet.Parameters;

namespace SpiceNet.Models
{
    /// <summary>
    /// The abstract definition of a Spice Model
    /// </summary>
    public abstract class Model : Parameterized
    {
        /// <summary>
        /// Gets a list of instances
        /// </summary>
        protected HashSet<Instance> Instances { get; } = new HashSet<Instance>();

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="name"></param>
        public Model(string name) : base(name) { }

        /// <summary>
        /// Get the number of instances using this model
        /// </summary>
        public int Count => Instances.Count;

        /// <summary>
        /// Add an instance to the model
        /// It will also remove this instance from the previous model list if necessary
        /// </summary>
        /// <param name="instance"></param>
        public void Add(Instance instance)
        {
            if (instance == null)
                throw new ArgumentNullException("instance");
            if (instance.ModelType != this.GetType())
                throw new InvalidModelException(this, instance);

            // Add to this model
            Instances.Add(instance);
        }

        /// <summary>
        /// Add instances to the model
        /// </summary>
        /// <param name="instances"></param>
        public void AddRange(IEnumerable<Instance> instances)
        {
            foreach (Instance instance in instances)
                Add(instance);
        }

        /// <summary>
        /// Clear all instances in this model
        /// </summary>
        public void Clear() => Instances.Clear();

        /// <summary>
        /// Remove instances from the list
        /// </summary>
        /// <param name="instances"></param>
        public void Remove(Instance instance) => Instances.Remove(instance);

        /// <summary>
        /// Find an instance by name
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public Instance Find(string name)
        {
            foreach (var inst in Instances)
                if (inst.Name == name)
                    return inst;
            return null;
        }

        /// <summary>
        /// Check if an instance is registered with the model
        /// </summary>
        /// <param name="inst"></param>
        /// <returns></returns>
        public bool Has(Instance inst) => Instances.Contains(inst);

        /// <summary>
        /// Setup the model
        /// </summary>
        /// <param name="ckt"></param>
        public virtual void Setup(Circuit ckt)
        {
            foreach (var inst in Instances)
                inst.Setup(ckt, this);
        }

        /// <summary>
        /// Unsetup the model
        /// </summary>
        /// <param name="ckt"></param>
        public virtual void Unsetup(Circuit ckt)
        {
            // Do nothing
            return;
        }

        /// <summary>
        /// Setup routine to process devices speciallyt for PZ analysis
        /// </summary>
        /// <param name="ckt"></param>
        public virtual void PzSetup(Circuit ckt)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Setup routine for the device sensitivity info
        /// </summary>
        /// <param name="ckt"></param>
        public virtual void SenSetup(Circuit ckt)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Do Temperature dependent initialization
        /// </summary>
        /// <param name="ckt"></param>
        public virtual void Temperature(Circuit ckt)
        {
            foreach (var inst in Instances)
                inst.Temperature(ckt, this);
        }

        /// <summary>
        /// Load the model and its instances
        /// </summary>
        /// <param name="ckt"></param>
        /// <returns></returns>
        public virtual void Load(Circuit ckt)
        {
            foreach (var inst in Instances)
                inst.Load(ckt, this);
        }

        /// <summary>
        /// Load the AC model and its instances
        /// </summary>
        /// <param name="ckt"></param>
        public virtual void AcLoad(Circuit ckt)
        {
            foreach (var inst in Instances)
                inst.AcLoad(ckt, this);
        }

        /// <summary>
        /// Routine to load for pole-zero analysis
        /// </summary>
        /// <param name="ckt"></param>
        public virtual void PzLoad(Circuit ckt)
        {
            foreach (var inst in Instances)
                inst.PzLoad(ckt, this);
        }

        /// <summary>
        /// Routine to load the device sensitivity info
        /// </summary>
        /// <param name="ckt"></param>
        public virtual void SenLoad(Circuit ckt)
        {
            foreach (var inst in Instances)
                inst.SenLoad(ckt, this);
        }

        /// <summary>
        /// Routine to update the device sensitivity info
        /// </summary>
        /// <param name="ckt"></param>
        public virtual void SenUpdate(Circuit ckt)
        {
            foreach (var inst in Instances)
                inst.SenUpdate(ckt, this);
        }

        /// <summary>
        /// Ask if the current solution can be accepted
        /// </summary>
        /// <param name="ckt"></param>
        public virtual void Accept(Circuit ckt)
        {
            return;
        }

        /// <summary>
        /// Routine to load the device AC sensitivity info
        /// </summary>
        /// <param name="ckt"></param>
        public virtual void SenAcLoad(Circuit ckt)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Distortion routine
        /// </summary>
        /// <param name="ckt"></param>
        public virtual void Distortion(Circuit ckt)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Noise routine
        /// </summary>
        /// <param name="ckt"></param>
        public virtual void NoiseLoad(Circuit ckt, 
            Noise.OutputMode mode, 
            Noise.Operation operation,
            NoiseData data,
            ref double outNdens)
        {
            // By default, the device does not generate noise
            return;
        }

        /// <summary>
        /// Set the IC conditions for this device
        /// </summary>
        /// <param name="ckt"></param>
        public virtual void SetIc(Circuit ckt)
        {
            return;
        }

        /// <summary>
        /// Check convergence
        /// </summary>
        /// <param name="ckt"></param>
        /// <returns>True if error</returns>
        public virtual bool IsConvergent(Circuit ckt) => true;
    }
}
